package com.shuo.framework.servlet;

import com.shuo.framework.annotation.GGController;
import com.shuo.framework.annotation.GGRequestMapping;
import com.shuo.framework.annotation.GGRequestParam;
import com.shuo.framework.context.GGApplicationContext;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by shuoGG on 2018/9/12 0012
 */
public class GGDispatcherServlet extends HttpServlet {

    private static final String LOCATION = "contextConfigLocation";

    private List<Handler> handlerMapping = new ArrayList<>();

    private Map<Handler, HandlerAdapter> adapterMapping = new HashMap<>();

    private List<ViewResolver> viewResolvers = new ArrayList<>();


    @Override
    public void init(ServletConfig config) {
        GGApplicationContext context = new GGApplicationContext(config.getInitParameter(LOCATION));
        //请求解析
        initMultipartResolver(context);
        //多语言、国际化
        initLocaleResolver(context);
        //主题View层的
        initThemeResolver(context);

        //解析url和Method的关联关系
        initHandlerMappings(context);
        //适配器（匹配的过程）
        initHandlerAdapters(context);

        //异常解析
        initHandlerExceptionResolvers(context);
        //视图转发（根据视图名字匹配到一个具体模板）
        initRequestToViewNameTranslator(context);

        //解析模板中的内容（拿到服务器传过来的数据，生成HTML代码）
        initViewResolvers(context);

        initFlashMapManager(context);
        System.out.println("GG Spring MVC is init.");
    }


    /**
     * 处理@GGRequestMapping
     * <p>
     * 生成url-Method的映射表
     *
     * @param context
     */
    private void initHandlerMappings(GGApplicationContext context) {
        Map<String, Object> ioc = context.getAll();
        if (ioc.isEmpty()) return;
        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(GGController.class)) continue;
            String url = "";
            if (clazz.isAnnotationPresent(GGRequestMapping.class)) {
                GGRequestMapping requestMapping = clazz.getAnnotation(GGRequestMapping.class);
                url += ("/" + requestMapping.value());
            }
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(GGRequestMapping.class)) continue;
                GGRequestMapping requestMapping = method.getAnnotation(GGRequestMapping.class);
                url += ("/" + requestMapping.value());
                String regex = url.replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);
                // url-pattern, Controller实例, 方法对象
                handlerMapping.add(new Handler(pattern, entry.getValue(), method));
            }
        }
    }

    /**
     * 处理@GGRequestParam
     *
     * @param context
     */
    private void initHandlerAdapters(GGApplicationContext context) {
        if (handlerMapping.isEmpty()) return;
        // 参数名, 参数顺序
        Map<String, Integer> paramMapping = new HashMap<>();
        for (Handler handler : handlerMapping) {
            // 获取方法参数的类
            Class<?>[] paramsTypes = handler.method.getParameterTypes();

            for (int i = 0; i < paramsTypes.length; i++) {
                Class<?> type = paramsTypes[i];
                if (type == HttpServletResponse.class || type == HttpServletRequest.class) {
                    paramMapping.put(type.getName(), i);
                }
            }
            Annotation[][] pa = handler.method.getParameterAnnotations();
            for (int i = 0; i < pa.length; i++) {
                for (Annotation a : pa[i]) {
                    if (a instanceof GGRequestParam) {
                        String paramName = ((GGRequestParam) a).value();
                        if (!"".equals(paramName.trim())) {
                            paramMapping.put(paramName, i);
                        }
                    }
                }
            }
            adapterMapping.put(handler, new HandlerAdapter(paramMapping));
        }
    }

    /**
     * 生成viewName到模板文件的映射
     *
     * @param context
     */
    private void initViewResolvers(GGApplicationContext context) {
        String templateRoot = context.getConfig().getProperty("templateRoot");
        String rootPath = this.getClass().getClassLoader().getResource(templateRoot).getFile();
        File rootDir = new File(rootPath);
        for (File template : rootDir.listFiles()) {
            viewResolvers.add(new ViewResolver(template.getName(), template));
        }
    }


    private void initMultipartResolver(GGApplicationContext context) {
    }

    private void initLocaleResolver(GGApplicationContext context) {
    }

    private void initThemeResolver(GGApplicationContext context) {
    }

    private void initHandlerExceptionResolvers(GGApplicationContext context) {
    }

    private void initRequestToViewNameTranslator(GGApplicationContext context) {
    }


    private void initFlashMapManager(GGApplicationContext context) {
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }


    //在这里调用自己写的Controller的方法
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            resp.getWriter().write("500 Exception, Msg :" + Arrays.toString(e.getStackTrace()));
        }
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
        try {

            Handler handler = getHandler(req);
            if (handler == null) {
                resp.getWriter().write("404 Not Found");
                return;
            }
            HandlerAdapter handlerAdapter = getHandlerAdapter(handler);
            GGModelAndView modelAndView = handlerAdapter.handle(req, resp, handler);
            responseTemplate(resp, modelAndView);
        } catch (IOException e) {
            e.printStackTrace();
            throw e;
        }
    }

    private HandlerAdapter getHandlerAdapter(Handler handler) {
        return adapterMapping.get(handler);
    }

    private Handler getHandler(HttpServletRequest req) {
        if (handlerMapping.isEmpty()) return null;
        String contextPath = req.getContextPath();
        String realUri = req.getRequestURI().replace(contextPath, "").replaceAll("/+", "/");
        for (Handler handler : handlerMapping) {
            Matcher matcher = handler.pattern.matcher(realUri);
            if (matcher.matches()) {
                return handler;
            }
        }
        return null;
    }

    /**
     * 将ModelAndView输出出去
     *
     * @param resp
     * @param mv
     */
    public void responseTemplate(HttpServletResponse resp, GGModelAndView mv) {
        if (mv == null) return;
        if (viewResolvers.isEmpty()) return;
        try {
            for (ViewResolver viewResolver : viewResolvers) {
                if (!mv.getView().equals(viewResolver.getViewName())) continue;
                String html = viewResolver.parse(mv);
                resp.getWriter().write(html);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 目的是映射 url到Method
     * url-pattern, Controller实例, 方法对象
     */
    private static class Handler {
        protected Object controller;
        protected Method method;
        protected Pattern pattern;

        protected Handler(Pattern pattern, Object controller, Method method) {
            this.pattern = pattern;
            this.controller = controller;
            this.method = method;
        }
    }

    /**
     * 对Handler再封装一层: 多加了params与对ModelAndView的处理
     */
    private static class HandlerAdapter {
        // Integer记录的是参数的index位置, 需要index是因为invoke需要参数数组按顺序来
        private Map<String, Integer> paramMapping;

        public HandlerAdapter(Map<String, Integer> paramMapping) {
            this.paramMapping = paramMapping;
        }

        public GGModelAndView handle(HttpServletRequest req, HttpServletResponse resp, GGDispatcherServlet.Handler handler) throws Exception {
            Class<?>[] paramTypes = handler.method.getParameterTypes();
            // 最后用来反射调用方法注入的实参
            Object[] paramValues = new Object[paramTypes.length];
            Map<String, String[]> reqParams = req.getParameterMap();
            // 根据请求param的key从paramMapping中获取参数对应的index
            for (Map.Entry<String, String[]> param : reqParams.entrySet()) {
                // 正则\\[|\\]匹配\\或\|
                String value = Arrays.toString(param.getValue()).replaceAll("\\[|\\]", "").replaceAll(",\\s", ",");
                if (!this.paramMapping.containsKey(param.getKey())) continue;
                int index = this.paramMapping.get(param.getKey());
                // 将请求param的value转换成java方法的形参类型
                paramValues[index] = strToJavaObj(value, paramTypes[index]);
            }
            // 反射的method参数有HttpServletRequest则注入request参数
            String reqName = HttpServletRequest.class.getName();
            if (this.paramMapping.containsKey(reqName)) {
                int reqIndex = this.paramMapping.get(reqName);
                paramValues[reqIndex] = req;
            }
            // 反射的method参数有HttpServletResponse则注入response参数
            String respName = HttpServletResponse.class.getName();
            if (this.paramMapping.containsKey(respName)) {
                int respIndex = this.paramMapping.get(respName);
                paramValues[respIndex] = resp;
            }
            Object r = handler.method.invoke(handler.controller, paramValues);
            boolean isModelAndView = handler.method.getReturnType() == GGModelAndView.class;
            if (isModelAndView) {
                return (GGModelAndView) r;
            } else {
                return null;
            }
        }

        /**
         * String转对应的java Type, 主要为了应付requestParam的value的类型转换
         *
         * @param value
         * @param clazz
         * @return
         */
        private Object strToJavaObj(String value, Class<?> clazz) {
            if (clazz == String.class) {
                return value;
            } else if (clazz == Integer.class) {
                return Integer.valueOf(value);
            } else if (clazz == int.class) {
                return Integer.valueOf(value).intValue();
            } else {
                return null;
            }
        }
    }

    private static class ViewResolver {
        private String viewName;
        private File file;

        protected ViewResolver(String viewName, File file) {
            this.viewName = viewName;
            this.file = file;
        }

        public String parse(GGModelAndView modelAndView) {
            return null;
        }

        public String getViewName() {
            return viewName;
        }
    }
}
